跳到主要内容
Cowers://
全部文章
推理范式

Reflexion 反思式自我纠错

Reflexion 的核心是把失败信号转成一段自然语言经验,写进记忆,再喂给下一次尝试。它用「语言反馈」代替了「梯度更新」——模型权重不动,靠上下文里多出来的那段教训变强。

Reflexion:反思式自我纠错

阅读提示 面向已经写过「失败就重试」逻辑的开发者。 前置概念:Agent 循环、工具调用返回值(测试结果 / 报错信息)。 ⚠️ = 常见陷阱 🆚 = 对比说明 💡 = 选择建议

目录


核心概念 Reflexion 的核心是把失败信号转成一段自然语言经验,写进记忆,再喂给下一次尝试。它用「语言反馈」代替了「梯度更新」——模型权重不动,靠上下文里多出来的那段教训变强。

一、一句话定义

做错了以后,不只是重试,而是先总结自己为什么错,再带着这条教训重试。

二、最小示例:一次自我修复

任务:写代码解决一道算法题。

第一轮:
  生成代码

  运行测试 → 失败:IndexError on empty input

  Reflection(模型自己写的):
  "我假设输入数组至少有一个元素,没处理 nums 为空的情况。
   下次先在函数开头加空数组的早返回。"
 
第二轮:
  把上面那段 Reflection 拼进 prompt

  重新生成代码(这次带了 if not nums: return 0)

  测试通过 ✅

抽象成循环:

Action

Result(测试结果 / 报错 / 评分)

Reflection(自然语言教训,存入记忆)

Improved Action

三、🆚 和普通重试的区别

这是整个概念的重点,两者代码看起来都是 for i in range(3),但中间那一步完全不同。

普通重试:

失败 → 再试一次(prompt 一个字都没变)

Reflexion:

失败

分析为什么失败      ← 多出来的关键一步

记录经验(写入记忆)

带着经验重新尝试     ← prompt 变了
维度 普通重试 Reflexion
下一次的输入 与上次完全相同 多了一段失败教训
失败信息利用 丢弃 转成自然语言存下来
多次失败 大概率重复同一个错 教训累积,错误面收敛
额外成本 每轮多一次生成反思的调用

为什么普通重试大概率重复同一个错 LLM 采样虽有随机性,但在同一个 prompt 下,输出分布的众数是稳定的。同样的输入重采样三次,很可能三次都掉进同一个思维定式。Reflexion 之所以有效,是因为它改变了输入,从而改变了整个输出分布,而不是在原分布里碰运气。

四、机制:三个角色和一块记忆

一个完整实现通常包含:

角色 职责 实现方式
Actor 产出动作/代码/答案 LLM 生成,输入 = 任务 + 短期轨迹 + 长期反思
Evaluator 评价这次做得怎么样,给出反馈 单元测试、编译器、外部打分器、启发式规则,或另一个 LLM
Self-Reflection 把「轨迹 + 反馈」写成一段语言化的教训 LLM 生成
Memory 短期存本次轨迹,长期存历次反思 见下方说明

⚠️ 把 Evaluator 和 Self-Reflection 当成同一个东西 很多二手资料(包括本笔记的旧版本)会写成「Evaluator 负责生成反思」。原论文里这是两个分开的组件,拆开才说得通:

  • Evaluator 回答「做得好不好」——输出是奖励值、通过/失败、或一段错误信息。它可以完全不是 LLM(跑一遍 pytest 就是个 Evaluator)。
  • Self-Reflection 回答「为什么不好、下次怎么改」——输入是「执行轨迹 + Evaluator 的反馈」,输出是一段自然语言教训。这一步必须是 LLM。

合成一个会导致一个很实际的后果:你以为「换成 pytest 当 Evaluator」就完事了,但 pytest 只会吐报错,没有人把报错翻译成教训——记忆里存的是原始 stack trace,下一轮 Actor 读到的还是同一段报错,等于退化成普通重试。

两块记忆,别只留一块

原论文里 Memory 分两层,实现时最常被漏掉的是第一层:

  • 短期记忆 = 本次尝试的执行轨迹(做了哪些动作、每步的观察结果)。它是 Self-Reflection 的输入之一——没有轨迹,反思就只能对着最终结果猜「哪一步错了」。
  • 长期记忆 = 历次反思的集合(跨 trial 累积),拼进下一轮 Actor 的 prompt。

只用一个 list[str] 存反思,等于只做了长期那一层。

最小骨架:

long_term = []                        # 跨 trial 累积的反思
 
for attempt in range(MAX_ATTEMPTS):
    # trajectory 是本次尝试的短期记忆:动作 + 每步观察
    action, trajectory = actor(task, long_term)
    feedback = evaluator(action, trajectory)      # 只回答「好不好」
    if feedback.passed:
        return action
 
    # 反思的输入必须包含轨迹,否则它不知道是哪一步出的问题
    reflection = reflect(task, trajectory, feedback)
    long_term = [*long_term, reflection]          # 不可变追加
 
raise MaxAttemptsExceeded(long_term)

Evaluator 的质量决定上限 反思的输入是评估结果。如果 Evaluator 只能说「错了」,反思就只能瞎猜;如果 Evaluator 能给出 IndexError: list index out of range, line 7,反思就能精确定位。优先用确定性的评估器(测试、编译器、schema 校验),而不是让 LLM 给自己打分。

但也别走到另一个极端:Evaluator 的输出不必是二值的。原论文同时支持标量奖励、二值成败和自由文本反馈——在 AlfWorld 这类决策任务里用的就是启发式规则(比如「同一个动作重复了 N 次还没进展」判定失败),并不存在单元测试。信号要具体、要可归因,但不要求非黑即白。

五、⚠️ 核心陷阱:反思写成了废话

⚠️ 反思空洞化 错误操作: 反思 prompt 写成「请反思上一次的错误」,然后直接把输出拼进记忆。

实际结果: 记忆里堆的是这种句子:

"我应该更仔细一些。"
"下次要考虑边界情况。"
"需要更全面地理解题目。"

三轮之后,prompt 变长了、成本涨了,但错误一模一样。

原因: 这些句子不含任何新增信息。它们对下一次生成没有约束力——模型读完「要考虑边界情况」,依然不知道是哪个边界。反思只有携带具体、可执行的修正指令时才改变输出分布。

正确做法: 在反思 prompt 里强制要求结构和具体性,例如:

用三句话回答,禁止使用"更仔细""更全面"这类词:
1. 具体哪一行 / 哪个假设错了?
2. 为什么这个假设不成立?
3. 下次要写的具体改动是什么?(给出代码片段或明确条件)

好的反思长这样:「第 7 行 nums[0] 假设数组非空;测试用例 [] 会触发 IndexError;下次在函数开头加 if not nums: return 0。」

⚠️ 记忆无限增长 错误操作: 每轮反思都追加进 memory,永不清理。

实际结果: 第 10 轮时 prompt 里塞了 10 段教训,其中大半是早已修复的问题;模型注意力被稀释,甚至会「改回」之前已经修好的地方。

原因: 上下文里的每一条指令都在争夺注意力,过期教训和有效教训权重相同。

正确做法: 限制记忆窗口(常见做法是只保留最近 3 条),或在每轮让模型把历史反思合并压缩成一份「当前有效约束清单」。

六、什么时候用

用:

  • Coding Agent:有单元测试当 Evaluator,信号最干净,收益最大
  • 自动调试、编译错误修复
  • 有明确成败判定的任务(游戏、benchmark、schema 校验)

不用:

  • 完全拿不到可归因反馈的开放式任务(写文案、做总结)——注意判据是「反馈能不能指向具体哪里不行」,不是「有没有单元测试」。开放任务如果有明确评分维度(字数、必含要点、风格清单)或真人评语,Reflexion 一样能用;只有当反馈退化成一个说不清所以然的总分时,反思才变成自说自话。
  • 一次就该做对的低延迟场景——Reflexion 至少让延迟和成本翻倍

七、复习重点

复习重点

  1. 定义:失败 → 生成自然语言教训 → 存入记忆 → 带教训重试。用语言反馈代替梯度更新。
  2. 和重试的本质差别:重试不改 prompt(同分布重采样,大概率同样出错),Reflexion 改 prompt(换了输出分布)。
  3. 三个角色分工别混:Actor 执行、Evaluator 评价并给反馈Self-Reflection 把反馈写成教训。后两个是分开的组件——只换 Evaluator 不接 Self-Reflection,等于退化成普通重试。
  4. 两块记忆:短期 = 本次执行轨迹(Self-Reflection 的输入),长期 = 历次反思。只用一个 list[str] 是漏了短期那块。
  5. 最容易出错的地方:反思空洞化。必须强制输出「哪里错 / 为什么错 / 具体怎么改」,禁用「更仔细」类空话。
  6. 第二个坑:记忆无限增长会稀释注意力,要限窗口或做压缩合并。
  7. 前提条件:Evaluator 质量决定反思质量,优先用测试、编译器这类确定性信号。但反馈不必是二值的——标量奖励、自由文本、启发式规则都行,要求是「可归因」而非「非黑即白」。
  8. 它管哪一层:失败回路。和 Plan-and-Execute 的 Replan 天然互补——反思产出的教训正好是重新规划的输入。

相关笔记:Agent 推理范式总览Plan-and-Execute 先规划再执行Self-Ask 自问自答多跳推理ToT 思维树